/*************************************************************************************
 * Nombre del archivo: Generador_Analizador_lexico.java
 * Universidad del Valle de Guatemala
 * Curso: Diseño de Lenguajes de Programación
 * @author martin
 *          Martín Guzmán 08041
 * Fecha: Abril de 2010
 * Descripción: Generador de analizador léxico
*************************************************************************************/

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;

public class Generador_Analizadores {
    
    LinkedList<String> file_lines = new LinkedList<String>();


    //--------------------------------------------------------------------------
    //##########################################################################
        // ------- vocabulario de cocol ----------------------------------------
        String letter="[a-zA-Z]";
        String digit="\\d";
        String number=digit+"("+digit+")*";
        String ident=letter+"("+letter+"|"+digit+")*";
        String string="\""+"("+number+"|"+letter+"|[^\\\"])*"+"\"";
        String character="\'"+"("+number+"|"+letter+"|[^\\s])"+"\'";
        String space="(\\s)*";
        // ---------------------------------------------------------------------
    //##########################################################################
    //##########################################################################
        static HashMap<String,String> Characters_SetDecl = new LinkedHashMap<String,String>();
        static HashMap<String,String> Characters_SetDecl_Java = new LinkedHashMap<String,String>();
        static HashMap<String,String> Keywords_KeywordDecl = new LinkedHashMap<String,String>();
        static HashMap<String,String> Keywords_KeywordDecl_Java = new LinkedHashMap<String,String>();
        static HashMap<String,String> Tokens_TokenDecl = new LinkedHashMap<String,String>();
        static HashMap<String,String> Productions = new LinkedHashMap<String,String>();
        static String nombre_scanner = "";
    //##########################################################################
    //##########################################################################
        String descripcion_error = "";
        boolean error=false;
    //##########################################################################

    /***********************************************
     * Método: Generador_Analizador_Lexico
     * Uso: Constructor
     * @param file_lines
     * @param path contiene la carpeta en donde se creará el generador
    ***********************************************/
    public Generador_Analizadores() {
        Characters_SetDecl = new LinkedHashMap<String,String>();
        Characters_SetDecl_Java = new LinkedHashMap<String,String>();
        Keywords_KeywordDecl = new LinkedHashMap<String,String>();
        Keywords_KeywordDecl_Java = new LinkedHashMap<String,String>();
        Tokens_TokenDecl = new LinkedHashMap<String,String>();
        nombre_scanner = "";
    }

    /***********************************************
     * Método: Generador_Analizador_Lexico
     * Uso: Constructor
     * @param file_lines
     * @param path contiene la carpeta en donde se creará el generador
    ***********************************************/
    public Generador_Analizadores(LinkedList<String> file_lines){

        Characters_SetDecl = new LinkedHashMap<String,String>();
        Characters_SetDecl_Java = new LinkedHashMap<String,String>();
        Keywords_KeywordDecl = new LinkedHashMap<String,String>();
        Keywords_KeywordDecl_Java = new LinkedHashMap<String,String>();
        Tokens_TokenDecl = new LinkedHashMap<String,String>();
        nombre_scanner = "";

        this.file_lines.clear();
        this.Agregar_a_lista(file_lines, this.file_lines);
        Main.PrintDB(this.file_lines.toString());
        String todo_archivo = this.Copiar_a_cadena(0);

        Main.PrintDB("TODO EL ARCHIVO: \n"+todo_archivo+"\n--------------------------------------------------\n\n\n");
        Generador_Analizadores.Characters_SetDecl.put("ANY", "CHR(0)..CHR(255)");
        //try{
            if(DefCocol.Cocol(todo_archivo).aceptado==true){
                Main.Print("\n***********************************************************");
                Main.Print("Archivo con sintaxis de Cocol correcta");
                Main.Print("\n***********************************************************\n");
                Main.Print("CHARACTERS:\n"+Characters_SetDecl.toString());
                Main.Print("\nKEYWORDS:\n"+Keywords_KeywordDecl.toString());
                Main.Print("\nTOKENS:\n"+Tokens_TokenDecl.toString());
                Main.Print("\nPRODUCTIONS:\n"+Productions.toString());
                //Main.Print("\nCantidad de PRODUCTIONS:\n"+Productions.size());
                this.Convertir_Characters_A_Java(this.Characters_SetDecl);
                if(this.error==false){
                    Main.PrintDB("CHARACTERS:\n"+Characters_SetDecl_Java.toString());
                    Main.PrintDB("\nKEYWORDS:\n"+Keywords_KeywordDecl.toString());
                    Main.PrintDB("\nTOKENS:\n"+Tokens_TokenDecl.toString());

                    Generador_Codigo_Scanner codGenScan = new Generador_Codigo_Scanner(this.Characters_SetDecl_Java,this.Keywords_KeywordDecl,this.Tokens_TokenDecl,nombre_scanner);
                    Generador_Codigo_Parser codGenPar = new Generador_Codigo_Parser(this.Productions);
                }
            }
            else{
                Main.Print("\n***********************************************************");
                Main.Print("Archivo de entrada no válido acorde a la sintaxis de Cocol");
                Main.Print("\n***********************************************************");
                Main.Print("\nCHARACTERS:\n"+Characters_SetDecl.toString());
                Main.Print("\nKEYWORDS:\n"+Keywords_KeywordDecl.toString());
                Main.Print("\nTOKENS:\n"+Tokens_TokenDecl.toString());
                Main.Print("\nPRODUCTIONS:\n"+Productions.toString());
            }
        /*
        }catch(Exception exp){
            Main.Print(exp.getStackTrace().toString());
        }
         */
    }

    /***********************************************
     * Método: Agregar_a_lista
     * Uso: Agrega elementos de la lista fuente a la lista destino
     * @param fuente
     * @param destino
    ***********************************************/
    public void Agregar_a_lista(List fuente, List destino){
        for(int i=0;i<fuente.size();i++){
            destino.add(fuente.get(i));
        }
    }

    /***********************************************
     * Método: Copiar_a_cadena
     * Uso: Agrega a una cadena todas las líneas
     *      a partir del índice ingresado
     * @param a contiene el índice de donde empezar a copiar
    ***********************************************/
    public String Copiar_a_cadena(int a){
        String retorno = "";
        try{
            retorno = this.file_lines.get(a);
        }catch(Exception exp){
            Main.PrintDB("Error al copiar las líneas del archivo a una cadena...");
            return "";
        }
        for(int i=a+1;i<this.file_lines.size();i++){
            retorno+="\n"+this.file_lines.get(i);
        }
        return retorno;

    }

    /***********************************************
     * Método: Cortar_cadena
     * Uso: Corta la cadena hasta el índice i
     * @param a contiene el índice hasta donde cortar
     * @param cadena contiene la cadena a cortar
    ***********************************************/
    public String Cortar_cadena(String cadena, int a){
        try{
            String cadena2 = "";
            cadena2 = cadena.substring(0, a);
            Main.PrintDB("Cadena cortada: "+cadena2);
            return cadena2;
        }catch(Exception exp){
            Main.PrintDB("Error al copiar cortar la cadena: "+cadena+" en el índice: "+a);
        }
        return "";

    }

    /***********************************************
     * Método: Complementar_cadena
     * Uso: Corta la cadena hasta el índice i
     * @param a contiene el índice hasta donde cortar
     * @param cadena contiene la cadena a cortar
    ***********************************************/
    public static String Complementar_cadena(String cadena, int a){
        try{
            String cadena2 = "";
            cadena2 = cadena.substring(a);
            //Main.PrintDB("Cadena cortada: "+cadena2);
            return cadena2;
        }catch(Exception exp){
            Main.PrintDB("Error al copiar cortar la cadena: "+cadena+" en el índice: "+a);
        }
        return "";

    }

    /***********************************************
     * Método: Convertir_Characters
     * Uso: Cambia lo que está un hashMap a expr
     * @param cadena
     * @return Retorno_Funcion
    ***********************************************/
    public void Convertir_Characters_A_Java(HashMap<String,String> hashMap){

        Iterator iterator = hashMap.keySet().iterator();
        HashMap<String,String> hashResult = new LinkedHashMap<String,String>();

        while( iterator. hasNext() ){
            String cadena = (String) iterator.next();
            String cadena2 = (String) hashMap.get(cadena);
            Main.PrintDB("\n#) "+cadena+" = "+cadena2);
            String cadena_result = this.conversion_char(cadena2);
            if(this.error==true){
                //hubo un error en la conversión de los CHARACTERS
                hashResult = null;
                break;
            }
            cadena_result = this.Operaciones_Conjuntos(cadena_result);
            Main.PrintDB("cadena_result: "+cadena_result);
            hashResult.put(cadena, cadena_result);
        }
        if(hashResult!=null){
            this.Characters_SetDecl_Java = hashResult;
            iterator = this.Characters_SetDecl_Java.keySet().iterator();
            Main.PrintDB("\n---------------- YA CAMBIADO --------------------\n");
            while( iterator. hasNext() ){
                String cadena = (String) iterator.next();
                String cadena2 = (String) this.Characters_SetDecl_Java.get(cadena);
                Main.PrintDB("#) "+cadena+" = "+cadena2);
            }
        }
    }

    /***********************************************
     * Método: conversion_char
     * Uso: Cambia lo que está un hashMap a expr
     * @param cadena
     * @return Retorno_Funcion
    ***********************************************/
    public String conversion_char(String cadena2) {

        String cadena = cadena2;
        String cadena_result = "";
        int indice_tamano = 0;

        LinkedList<Retorno_Funcion> lista = new LinkedList<Retorno_Funcion>();
        Retorno_Funcion ret1 = DefCocol.expect(this.string, cadena);
        Retorno_Funcion ret2 = DefCocol.Char_BasicSet(cadena);
        Retorno_Funcion ret3 = DefCocol.expect(this.space+"\\+"+this.space, cadena);
        Retorno_Funcion ret4 = DefCocol.expect(this.space+"\\-"+this.space, cadena);
        Retorno_Funcion ret5 = DefCocol.expect(this.ident, cadena);
        //string
        if(ret1.aceptado==true){

            String a = ret1.cadena.substring(1,ret1.cadena.length()-1);
            cadena_result+="["+(int)a.charAt(0);
            for(int i=1;i<a.length();i++){
                cadena_result+=","+(int)a.charAt(i);
            }
            cadena_result+="]";
            indice_tamano = ret1.indice;
            //lista.add(ret1);
        }
        //Char[..Char]
        else if(ret2.aceptado==true){
            //TODO arreglar para Char
            String[] cad = ret2.cadena.split("\\.\\.");
            if(cad.length==1){
                char ch = this.Char_String(cad[0]);
                cadena_result+="["+(int)ch+"]";
            }
            else{
                char ch1 = this.Char_String(cad[0]);
                char ch2 = this.Char_String(cad[1]);
                //revisar si el límite inferior es mayor.. entonces hay un error
                if(ch1>ch2){
                    this.error("Error. el límite inferior es mayor que el superior en: "+ret2.cadena);
                }else{
                    cadena_result+="["+(int)ch1;
                    for(int i=ch1+1;i<=ch2;i++){
                        cadena_result+=","+(int)i;
                    }
                    cadena_result+="]";
                }

            }
            Main.PrintDB("cadena result: "+cadena_result);
            indice_tamano = ret2.indice;
            //lista.add(ret2);
        }
        //+
        else if(ret3.aceptado==true){
            cadena_result +="+";
            indice_tamano = ret3.indice;
            //lista.add(ret3);
        }
        //-
        else if(ret4.aceptado==true){
            cadena_result +="-";
            indice_tamano = ret4.indice;
            //lista.add(ret4);
        }
        //ident
        else if(ret5.aceptado==true){
            String cadenaN = this.Characters_SetDecl.get(ret5.cadena);
            if(cadenaN == null){
                String cadena_error = "Error. Un identificador no definido: "+ret5.cadena;
                this.error(cadena_error);
                return "";
            }
            cadena_result += ""+this.conversion_char(cadenaN)+"";
            indice_tamano = ret5.indice;
            //lista.add(ret5);
        }
        //no hizo match con ningua de las opciones
        if(cadena_result.isEmpty() && ret3.aceptado==false){
            return "";
        }
        String retorno = "";
        //otra cadena contiene todo lo que va después del acutal...
        String otra_cadena = this.conversion_char(DefCocol.Complementar_cadena(cadena, indice_tamano));

        retorno = cadena_result + otra_cadena;
        return retorno;
    }

    /***********************************************
     * Método: error
     * Uso: Se llama cuando hay un error y activa una bandera
     * @param descripcion contiene la descripción del error
    ***********************************************/
    public void error(String descripcion){
        Main.Print("\n+++---+++---+++---+++---+++---+++---+++---+++---+++---+++\n");
        Main.Print("Descripción: "+descripcion);
        Main.Print("\n+++---+++---+++---+++---+++---+++---+++---+++---+++---+++\n");
        this.error=true;
    }

    /***********************************************
     * Método: Char_String
     * Uso: Para convertir de un Char de Cocol a un string
     * @param string contiene la cadena
    ***********************************************/
    public char Char_String(String string) {
        if(DefCocol.Char(string).aceptado==true){
            if(string.matches("CHR\\("+this.number+"\\)")){
                //CHR(num)
                char ch = (char)Integer.parseInt( string.substring(4,string.length()-1) );
                Main.PrintDB("Char: "+ch);
                return ch;
            }
            else{
                //'caracter'
                char ch = ( string.substring(1,string.length()-1) ).charAt(0);
                Main.PrintDB("Char: "+ch);
                return ch;
            }
        }
        return '¡';
    }

    /***********************************************
     * Método: Operaciones_Conjuntos
     * Uso: Sustituir todos los operadores +- en conjuntos
     * @param cadena1 contiene la cadena
    ***********************************************/
    public String Operaciones_Conjuntos(String cadena1) {

        LinkedHashSet<Integer> set = new LinkedHashSet<Integer>();
        String in = cadena1.substring(1, cadena1.indexOf("]"));
        Main.PrintDB(">>>>>>>>> ------- "+cadena1);
        Main.PrintDB(">>>>>>>>> ------- "+in);
        
        String[] split = in.split(",");
        for(int i=0;i<split.length;i++){
            set.add(Integer.parseInt(split[i]));
        }
        
        Main.PrintDB("Set: "+set.toString());
        Main.PrintDB("Cadena a quitar +-: "+cadena1);
        String cadena = DefCocol.Complementar_cadena(cadena1, cadena1.indexOf("]")+1);
        Main.PrintDB("Cadena a quitar +-: "+cadena);

        while(true){

            int suma = cadena.indexOf("+");
            int resta = cadena.indexOf("-");

            if(suma==-1 && resta==-1){
                break;
            }

            if((suma<resta && suma!=-1) || resta==-1){
                Main.PrintDB("quitar un + en: "+cadena);
                cadena = DefCocol.Complementar_cadena(cadena, 1);
                //suma va antes...
                Main.PrintDB("cadena: "+cadena);
                in = cadena.substring(1, cadena.indexOf("]"));
                Main.PrintDB("agregar la siguiente cadena: "+in);
                split = in.split(",");
                for(int i=0;i<split.length;i++){
                    set.add(Integer.parseInt(split[i]));
                }
                /*
                for(int a : in.toCharArray()){
                    set.add(a);
                }
                 */
                Main.PrintDB("Set: "+set.toString());
                cadena = DefCocol.Complementar_cadena(cadena, in.length()+2);
                //reemplaza la primera "]+[" por ""
                //cadena = cadena.substring(0,suma-1)+cadena.substring(suma+2,cadena.length());
            }
            else{
                Main.PrintDB("quitar un - en: "+cadena);
                cadena = DefCocol.Complementar_cadena(cadena, 1);
                //suma va antes...
                Main.PrintDB("cadena: "+cadena);
                in = cadena.substring(1, cadena.indexOf("]"));
                Main.PrintDB("agregar la siguiente cadena: "+in);
                split = in.split(",");
                for(int i=0;i<split.length;i++){
                    set.remove(Integer.parseInt(split[i]));
                }
                /*
                for(int a : in.toCharArray()){
                    set.remove(a);
                }
                 */
                Main.PrintDB("Set: "+set.toString());
                cadena = DefCocol.Complementar_cadena(cadena, in.length()+2);
            }
        }
        Main.PrintDB("Set: "+set.toString());
        //return cadena;
        return "{"+set.toString().substring(1,set.toString().length()-1)+"}";
    }
}
/*
//para ordenar la lista y seleccionar el match más grande..
Collections.sort(lista, new Comparator<Retorno_Funcion>() {
    public int compare(Retorno_Funcion o1, Retorno_Funcion o2) {
        Integer a1 = o1.indice;
        Integer a2 = o2.indice;
        return a2-a1;
    }
});
//string
if(lista.getFirst()==ret1){
    cadena_result += "["+ret1.cadena.substring(1,ret1.cadena.length()-1)+"]";
}
//ident
else if(lista.getFirst()==ret2){
    cadena_result += "("+this.conversion_char(this.Characters_SetDecl.get(ret2.cadena))+")";
}
//+
else if(lista.getFirst()==ret3){
    //TODO revisar el +
    cadena_result += "";
}
//-
else if(lista.getFirst()==ret4){
    //TODO arreglar para -
    cadena_result += "-";
}
//Char
else if(lista.getFirst()==ret5){
    //TODO arreglar para Char
    cadena_result += ret5.cadena;
}
 */